﻿/*
// Exepack.NET 
// http://www.codeplex.com/exepack
//
// Assembly information wrapper
// Written by Y [01-03-09]
// Copyright (c) 2008-2010 Alexey Yakovlev
*/

using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Windows.Forms;
using System.Configuration;

namespace Exepack
{
	using AsmDictionary = Dictionary<string, Assembly>;
	using System.ComponentModel;
	using Exepack.Properties;

	/// <summary>
	/// Loads assemblies and provides information about them
	/// </summary>
	public class AssemblyInformation : IComparable
	{
		private static AssemblyResolver Resolver { get; set; }

		static AssemblyInformation()
		{
			Resolver = new AssemblyResolver();
		}

		static void AddSearchDirectory(string path)
		{
			if (!string.IsNullOrEmpty(path))
				Resolver.AddSearchDirectory(Path.GetDirectoryName(path));
		}
		
		public AssemblyInformation(string path)
		{
			FullPath = path;

			FileName = Path.GetFileName(FullPath);
			Asm = LoadAssembly(FullPath);
			AsmName = new AssemblyName(Asm.FullName);
			AddSearchDirectory(path);
		}

		public AssemblyInformation(AssemblyName name)
		{
			AsmName = name;

			Asm = LoadAssembly(AsmName);
			FullPath = Asm.Location;
			FileName = Path.GetFileName(FullPath);
			AddSearchDirectory(FullPath);
		}

		public AssemblyInformation(Assembly asm)
		{
			Asm = LoadAssembly(asm);

			AsmName = new AssemblyName(asm.FullName);
			FullPath = asm.Location;
			FileName = Path.GetFileName(FullPath);
			AddSearchDirectory(FullPath);
		}

		public AssemblyInformation(Type type)
			: this(type.Assembly)
		{ 
		}

		[Browsable(false)]
		public IEnumerable<AssemblyInformation> References
		{
			get
			{
				foreach (AssemblyName asmName in Asm.GetReferencedAssemblies())
				{
					yield return new AssemblyInformation(asmName);
				}
			}
		}

		[Browsable(false)]
		public IList<AssemblyInformation> AllReferences
		{
			get
			{
				List<AssemblyInformation> refs = new List<AssemblyInformation>();
				GetAllReferences(refs, this);
				return refs;
			}
		}

		private void GetAllReferences(IList<AssemblyInformation> refs, AssemblyInformation root)
		{
			if (!root.Equals(this))
			{
				refs.Add(root);
			}

			foreach (AssemblyInformation asm in root.References)
			{
				if (refs.Any(a => a.FullName == asm.FullName) || asm.Equals(this))
					continue;

				GetAllReferences(refs, asm);
			}
		}

		Assembly LoadAssembly(string fullPath)
		{
			string path = fullPath.ToLower().Trim();
			if (!LoadedAssemblies.ContainsKey(path))
			{
				Assembly asm = Assembly.ReflectionOnlyLoadFrom(fullPath);
				LoadedAssemblies[path] = asm;
				LoadedAssemblies[asm.FullName] = asm;
				return asm;
			}

			return (Assembly)LoadedAssemblies[path];
		}

		Assembly LoadAssembly(AssemblyName asmName)
		{
			if (!LoadedAssemblies.ContainsKey(asmName.FullName))
			{
				Assembly asm;
				try
				{
					asm = Assembly.ReflectionOnlyLoad(asmName.FullName);
				}
				catch (FileLoadException)
				{
					asm = Resolver.Resolve(asmName);
				}
				catch (FileNotFoundException)
				{
					asm = Resolver.Resolve(asmName);
				}

				// reference can be redirected to some version already loaded
				if (!LoadedAssemblies.ContainsKey(asm.FullName))
				{
					LoadedAssemblies[asmName.FullName] = asm;
					LoadedAssemblies[asm.Location.ToLower().Trim()] = asm;
					return asm;
				}

				LoadedAssemblies[asmName.FullName] = LoadedAssemblies[asm.FullName];
			}

			return (Assembly)LoadedAssemblies[asmName.FullName];
		}

		Assembly LoadAssembly(Assembly asm)
		{
			LoadedAssemblies[asm.Location.ToLower().Trim()] = asm;
			LoadedAssemblies[asm.FullName] = asm;
			return asm;
		}

		private static AsmDictionary LoadedAssemblies = new AsmDictionary();

		[Browsable(false)]
		public Assembly Asm { get; private set; }

		[Browsable(false)]
		public AssemblyName AsmName { get; private set; }

		/// <summary>
		/// File name of the assembly (not including path)
		/// </summary>
		public string FileName { get; private set; }

		/// <summary>
		/// Full file name of the assembly (including path)
		/// </summary>
		public string FullPath { get; private set; }

		/// <summary>
		/// Full assembly name, including version information, culture and public key token
		/// </summary>
		public string FullName { get { return Asm.FullName; } }

		/// <summary>
		/// Gets file size of the assembly
		/// </summary>
		[Browsable(false)]
		public int FileSize { get { return (int)new FileInfo(FullPath).Length; } }

		/// <summary>
		/// Returns true if assembly is a valid PE image containing only MSIL code 
		/// which can be loaded in both 32-bit and 64-bit environments
		/// </summary>
		public bool IsILOnly
		{
			get
			{
				PortableExecutableKinds peKind;
				ImageFileMachine machine;
				Asm.ManifestModule.GetPEKind(out peKind, out machine);
				return peKind == PortableExecutableKinds.ILOnly;
			}
		}

		/// <summary>
		/// Returns true if image is Win32 Console application
		/// </summary>
		public bool IsConsoleApplication
		{
			get { return PeHeaderHelper.IsConsoleApplication(FullPath); }
		}

		/// <summary>
		/// Returns list of known .NET Framework assemblies
		/// </summary>
		static IList<string> FrameworkAssemblies = new List<string>(
			Resources.FrameworkAssemblies.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries));

		/// <summary>
		/// Returns true is the assembly is shipped with .NET Framework
		/// </summary>
		public bool IsSystemAssembly
		{
			get { return FrameworkAssemblies.Contains(AsmName.Name); }
		}

		/// <summary>
		/// Returns public key token as string
		/// </summary>
		public string PublicKeyToken
		{
			get { return BitConverter.ToString(AsmName.GetPublicKeyToken()).Replace("-", "").ToLower(); }
		}

		/// <summary>
		/// Returns name for compressed resource of the assembly
		/// </summary>
		[Browsable(false)]
		public string ResourceName
		{
			get { return Convert.ToBase64String(Encoding.UTF8.GetBytes(FullName.ToLowerInvariant())); }
		}
		
		public int CompareTo(object obj)
		{
			AssemblyInformation asmInfo = obj as AssemblyInformation;
			if (asmInfo == null)
				return 1;

			return AsmName.FullName.CompareTo(asmInfo.AsmName.FullName);
		}

		public override bool Equals(object obj)
		{
			return CompareTo(obj) == 0;
		}

		public override int GetHashCode()
		{
			return AsmName.FullName.GetHashCode();
		}

		public override string ToString()
		{
			return Asm.ToString() + (IsSystemAssembly ? ", System" : ", Non-System");
		}
	}
}
